package cn.com.idmy.cloud.service.impl;

import cn.com.idmy.base.exception.BizException;
import cn.com.idmy.base.model.Page;
import cn.com.idmy.base.util.Assert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import lombok.NonNull;
import org.dromara.hutool.core.collection.CollStreamUtil;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.reflect.FieldUtil;
import org.dromara.hutool.core.reflect.TypeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

public interface MybatisPlusDao<T, ID extends Serializable> extends BaseMapper<T> {
    @SuppressWarnings("unchecked")
    default @NotNull Class<T> entityType() {
        return (Class<T>) TypeUtil.getTypeArgument(getClass());
    }

    @SuppressWarnings("unchecked")
    default @NotNull Class<ID> idType() {
        return (Class<ID>) TypeUtil.getTypeArgument(getClass(), 1);
    }

    default @NotNull TableInfo table() {
        return TableInfoHelper.getTableInfo(entityType());
    }

    /**
     * 从 TableInfo 中获取字段对应的数据库列名
     *
     * @param fieldName Java 字段名（驼峰命名）
     * @return 数据库列名，如果找不到返回 null
     */
    default @Nullable String getColumnNameFromTableInfo(@NotNull String fieldName) {
        var tableInfo = table();
        // 检查是否为主键字段
        if (fieldName.equals(tableInfo.getKeyProperty())) {
            return tableInfo.getKeyColumn();
        }

        // 在普通字段中查找
        var tableFieldInfo = tableInfo.getFieldList().stream()
                .filter(field -> field.getProperty().equals(fieldName))
                .findFirst();

        return tableFieldInfo.map(TableFieldInfo::getColumn).orElse(null);
    }

    default @NotNull LambdaQueryWrapper<T> q() {
        return new LambdaQueryWrapper<>(entityType());
    }

    default @NotNull LambdaUpdateWrapper<T> u() {
        return new LambdaUpdateWrapper<>(entityType());
    }

    default @NotNull LambdaQueryWrapper<T> d() {
        return new LambdaQueryWrapper<>(entityType());
    }

    default long count(@NotNull LambdaQueryWrapper<T> q) {
        return selectCount(q);
    }

    default boolean has(@NotNull LambdaQueryWrapper<T> q) {
        return count(q) > 0;
    }

    default boolean has(@NonNull ID id) {
        return selectById(id) != null;
    }

    default void has(@NonNull ID id, @NotNull String msg, @NotNull Object... params) {
        if (!has(id)) {
            throw new IllegalStateException(String.format(msg, params));
        }
    }

    default void has(@NotNull LambdaQueryWrapper<T> q, @NotNull String msg, @NotNull Object... params) {
        if (!has(q)) {
            throw new IllegalStateException(String.format(msg, params));
        }
    }

    default boolean notHas(@NotNull LambdaQueryWrapper<T> q) {
        return !has(q);
    }

    default boolean notHas(@NonNull ID id) {
        return !has(id);
    }

    default void notHas(@NonNull ID id, @NotNull String msg, @NotNull Object... params) {
        if (!notHas(id)) {
            throw new IllegalStateException(String.format(msg, params));
        }
    }

    default void notHas(@NotNull LambdaQueryWrapper<T> q, @NotNull String msg, @NotNull Object... params) {
        if (!notHas(q)) {
            throw new IllegalStateException(String.format(msg, params));
        }
    }

    default @NotNull List<T> list(@NotNull LambdaQueryWrapper<T> q) {
        return selectList(q);
    }

    default @NotNull List<T> list(@NotNull LambdaQueryWrapper<T> q, @NotNull String msg, @NotNull Object... params) {
        return Assert.notEmpty(list(q), msg, params);
    }

    default @NotNull List<T> all() {
        return list(q());
    }

    default @NotNull List<T> list(@Nullable Collection<ID> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        } else {
            return selectByIds(ids);
        }
    }

    default @NotNull List<T> list(@NotNull Collection<ID> ids, @NotNull String msg, @NotNull Object... params) {
        return Assert.notEmpty(list(ids), msg, params);
    }

    default @NotNull <R> List<R> list(@NotNull SFunction<T, R> field, @Nullable Collection<ID> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        } else {
            var q = q().select(field);
            var idsStr = ids.stream().map(String::valueOf).collect(Collectors.joining(","));
            q.apply(table().getKeyColumn() + " in (" + idsStr + ")");
            return selectList(q).stream().map(field).collect(Collectors.toList());
        }
    }

    default @NotNull <R> List<R> list(@NotNull SFunction<T, R> field, @NotNull LambdaQueryWrapper<T> q) {
        var ts = list(q.select(field));
        return CollStreamUtil.toList(ts, field);
    }

    default @Nullable T getNullable(@NotNull LambdaQueryWrapper<T> q) {
        return selectOne(q);
    }

    default @NotNull T get(@NotNull LambdaQueryWrapper<T> q, @NotNull String msg, @NotNull Object... params) {
        var t = getNullable(q);
        return Assert.notNull(t, msg, params);
    }

    default @NotNull T get(@NotNull LambdaQueryWrapper<T> q) {
        return get(q, "根据「查询条件」找不到「{}」", table().getTableName());
    }

    default @Nullable T getNullable(@NonNull ID id) {
        return selectById(id);
    }

    default @NotNull T get(@NonNull ID id) {
        var t = getNullable(id);
        return Assert.notNull(t, "根据主键「{}」找不到「{}」", id, table().getTableName());
    }

    default @Nullable <R> R getNullable(@NotNull SFunction<T, R> field, @NonNull ID id) {
        var q = q().select(field);
        q.apply(table().getKeyColumn() + " = {0}", id);
        T t = selectOne(q);
        return t == null ? null : field.apply(t);
    }

    default @NotNull <R> R get(@NotNull SFunction<T, R> field, @NonNull ID id) {
        R r = getNullable(field, id);
        if (r == null) {
            throw new BizException("根据主键「{}」找不到「{}」", id, table().getTableName());
        } else {
            return r;
        }
    }

    default @NotNull <R> R get(@NotNull SFunction<T, R> field, @NonNull ID id, @NotNull String msg, @NotNull Object... params) {
        return Assert.notNull(getNullable(field, id), msg, params);
    }

    default @Nullable <R> R getNullable(@NotNull SFunction<T, R> field, @NotNull LambdaQueryWrapper<T> q) {
        q.select(field);
        T t = getNullable(q);
        return t == null ? null : field.apply(t);
    }

    default @NotNull <R> R get(@NotNull SFunction<T, R> field, @NotNull LambdaQueryWrapper<T> q, @NotNull String msg, @NotNull Object... params) {
        return Assert.notNull(getNullable(field, q), msg, params);
    }

    default @NotNull <R> R get(@NotNull SFunction<T, R> field, @NotNull LambdaQueryWrapper<T> q) {
        R r = getNullable(field, q);
        if (r == null) {
            throw new BizException("根据主键「查询条件」找不到「{}」", table().getTableName());
        } else {
            return r;
        }
    }

    default @NotNull Map<ID, T> map(@Nullable ID[] ids) {
        if (ids == null || ids.length == 0) {
            return new HashMap<>();
        } else {
            var list = list(List.of(ids));
            var keyProperty = table().getKeyProperty();
            return CollStreamUtil.toIdentityMap(list, t -> (ID) FieldUtil.getFieldValue(t, keyProperty));
        }
    }

    default @NotNull Map<ID, T> map(@Nullable Collection<ID> ids) {
        if (CollUtil.isEmpty(ids)) {
            return new HashMap<>();
        } else {
            var list = list(ids);
            var keyProperty = table().getKeyProperty();
            return CollStreamUtil.toIdentityMap(list, t -> (ID) FieldUtil.getFieldValue(t, keyProperty));
        }
    }

    default @NotNull <R> Map<R, T> map(@NotNull SFunction<T, R> key, @NotNull LambdaQueryWrapper<T> q) {
        return CollStreamUtil.toIdentityMap(list(q), key::apply);
    }

    default @NotNull <K, V> Map<K, V> map(@NotNull SFunction<T, K> key, @NotNull SFunction<T, V> val, @NotNull LambdaQueryWrapper<T> q) {
        var list = list(q.select(key, val));
        var ret = new HashMap<K, V>(list.size());
        for (int i = 0, size = list.size(); i < size; i++) {
            T t = list.get(i);
            V v = val.apply(t);
            if (v != null) {
                ret.put(key.apply(t), v);
            }
        }
        return ret;
    }

    default @NotNull <IN> Page<T> page(@NonNull Page<IN> page, @NotNull QueryWrapper<T> q) {
        var mpPage = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<T>(page.pageNo(), page.pageSize());
        // 判断是否需要查询总数
        if (page.hasTotal() != null && !page.hasTotal()) {
            mpPage.setSearchCount(false);
        }
        // 处理排序
        if (page.sorts() != null && page.sorts().length > 0) {
            var sorts = page.sorts();
            for (int i = 0; i < sorts.length; i += 2) {
                if (i + 1 < sorts.length) {
                    var field = sorts[i];
                    var order = sorts[i + 1];
                    var columnName = getColumnNameFromTableInfo(field);
                    if (columnName != null) {
                        if ("desc".equalsIgnoreCase(order)) {
                            q.orderByDesc(columnName);
                        } else {
                            q.orderByAsc(columnName);
                        }
                    }
                }
            }
        }
        var result = selectPage(mpPage, q);
        return Page.of(result.getRecords(), page.pageNo(), page.pageSize(), result.getTotal());
    }

    default @NotNull <IN> Page<T> page(@NonNull Page<IN> page, @NotNull LambdaQueryWrapper<T> q) {
        var mpPage = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<T>(page.pageNo(), page.pageSize());
        if (page.hasTotal() != null && !page.hasTotal()) {
            mpPage.setSearchCount(false);
        }
        if (page.sorts() != null && page.sorts().length > 0) {
            var sorts = page.sorts();
            var orderByParts = new StringBuilder();
            for (int i = 0; i < sorts.length; i += 2) {
                if (i + 1 < sorts.length) {
                    var field = sorts[i];
                    var order = sorts[i + 1];
                    var columnName = getColumnNameFromTableInfo(field);
                    if (columnName != null) {
                        if (!orderByParts.isEmpty()) {
                            orderByParts.append(", ");
                        }
                        orderByParts.append(columnName);
                        if ("desc".equalsIgnoreCase(order)) {
                            orderByParts.append(" desc");
                        } else {
                            orderByParts.append(" asc");
                        }
                    }
                }
            }
            if (!orderByParts.isEmpty()) {
                q.last("order by " + orderByParts);
            }
        }
        var result = selectPage(mpPage, q);
        return Page.of(result.getRecords(), page.pageNo(), page.pageSize(), result.getTotal());
    }

    default int create(@Nullable Collection<T> ls, int size) {
        if (CollUtil.isEmpty(ls)) {
            return 0;
        }
        // 分批插入
        int totalInserted = 0;
        List<List<T>> batches = ListUtil.partition(new ArrayList<>(ls), size);
        for (List<T> batch : batches) {
            for (T entity : batch) {
                totalInserted += insert(entity);
            }
        }
        return totalInserted;
    }

    default int create(@NonNull T t) {
        return insert(t);
    }

    default boolean createOrUpdate(@NonNull T t) {
        return insertOrUpdate(t);
    }

    default int update(@NonNull T t) {
        return updateById(t);
    }

    default int update(@Nullable Collection<T> ls, int size) {
        if (CollUtil.isEmpty(ls)) {
            return 0;
        }
        // 分批更新
        int totalUpdated = 0;
        List<List<T>> batches = ListUtil.partition(new ArrayList<>(ls), size);
        for (List<T> batch : batches) {
            for (T entity : batch) {
                totalUpdated += updateById(entity);
            }
        }
        return totalUpdated;
    }

    default int delete(@NonNull ID id) {
        return deleteById(id);
    }

    default int delete(@Nullable Collection<ID> ids) {
        if (CollUtil.isEmpty(ids)) {
            return 0;
        }
        return deleteByIds(ids);
    }

    default @Nullable T getNullable(@NotNull LambdaQueryWrapper<T> q, @NotNull SFunction<T, ?> f1, @NotNull SFunction<T, ?> f2) {
        return getNullable(q.select(f1, f2));
    }

    default @Nullable T getNullable(@NotNull LambdaQueryWrapper<T> q, @NotNull SFunction<T, ?> f1, @NotNull SFunction<T, ?> f2, @NotNull SFunction<T, ?> f3) {
        return getNullable(q.select(f1, f2, f3));
    }

    default @NotNull T get(@NotNull LambdaQueryWrapper<T> q, @NotNull SFunction<T, ?> f1, @NotNull SFunction<T, ?> f2) {
        return get(q.select(f1, f2));
    }

    default @NotNull T get(@NotNull LambdaQueryWrapper<T> q, @NotNull SFunction<T, ?> f1, @NotNull SFunction<T, ?> f2, @NotNull SFunction<T, ?> f3) {
        return get(q.select(f1, f2, f3));
    }

    default @NotNull List<T> list(@NotNull LambdaQueryWrapper<T> q, @NotNull SFunction<T, ?> f1, @NotNull SFunction<T, ?> f2) {
        return list(q.select(f1, f2));
    }

    default @NotNull List<T> list(@NotNull LambdaQueryWrapper<T> q, @NotNull SFunction<T, ?> f1, @NotNull SFunction<T, ?> f2, @NotNull SFunction<T, ?> f3) {
        return list(q.select(f1, f2, f3));
    }

    default @NotNull <K, V> Map<K, List<V>> maps(@NotNull SFunction<T, K> key, @NotNull SFunction<T, V> val, @NotNull LambdaQueryWrapper<T> q) {
        var list = list(q.select(key, val));
        var ret = new HashMap<K, List<V>>();
        for (T t : list) {
            K k = key.apply(t);
            V v = val.apply(t);
            if (v != null) {
                ret.computeIfAbsent(k, x -> new ArrayList<>()).add(v);
            }
        }
        return ret;
    }

}
